use std::future::{Ready, ready};

use actix_web::{
    Error, HttpMessage, HttpRequest, HttpResponse,
    body::EitherBody,
    dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
    http::{self, header::AUTHORIZATION},
};
use futures_util::future::LocalBoxFuture;
use log::{trace, warn};
use rbase::define::BaseError;

use crate::rest_api::{api_user::UserToken, service::srv_token::get_user_token};
use anyhow::Result;

pub struct CheckLogin;

impl<S, B> Transform<S, ServiceRequest> for CheckLogin
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type InitError = ();
    type Transform = CheckLoginMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(CheckLoginMiddleware { service }))
    }
}
pub struct CheckLoginMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for CheckLoginMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    dev::forward_ready!(service);

    fn call(&self, request: ServiceRequest) -> Self::Future {
        let mut is_logged_in = false;
        let token = request.headers().get(AUTHORIZATION);
        if let Some(auth_header) = token {
            if let Ok(token) = auth_header.to_str() {
                if let Ok(user) = get_user_token(token.to_string()) {
                    request.extensions_mut().insert(user);
                    is_logged_in = true;
                }
            }
        }
        // Don't forward to `/login` if we are already on `/login`.
        let path = request.path().to_lowercase();
        trace!("User is logged in: {} {} {:?}", path, is_logged_in, token);
        if !is_logged_in && !path.starts_with("/api/") {
            if path != "/login" {
                warn!("User is not logged in: {:?}", token);
                let (request, _pl) = request.into_parts();
                let response = HttpResponse::Found()
                    .insert_header((http::header::LOCATION, "/login"))
                    .finish()
                    // constructed responses map to "right" body
                    .map_into_right_body();
                return Box::pin(async { Ok(ServiceResponse::new(request, response)) });
            }
        }

        let res = self.service.call(request);

        Box::pin(async move {
            // forwarded responses map to "left" body
            res.await.map(ServiceResponse::map_into_left_body)
        })
    }
}

#[inline]
pub fn get_user_from_request(req: &HttpRequest) -> Result<UserToken, BaseError> {
    let user = req.extensions().get::<UserToken>().ok_or_else(|| BaseError::Unauthenticated)?.clone();
    Ok(user)
}

#[inline]
pub fn check_with_roles(user_token: &UserToken, required_roles: &[&str]) -> Result<(), BaseError> {
    if required_roles.is_empty() {
        return Ok(());
    }
    if user_token.roles.is_empty() {
        return Result::Err(BaseError::PermissionDenied);
    }

    for role in &user_token.roles {
        if required_roles.contains(&role.as_str()) {
            return Ok(());
        }
    }
    Err(BaseError::PermissionDenied)
}

pub fn check_is_admin(user_token: &UserToken) -> Result<(), BaseError> {
    if !user_token.is_admin {
        return Err(BaseError::PermissionDenied);
    }
    Ok(())
}
